﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using Esponce.Common;
using Esponce.QRCode.Models;
#if WINRT
using Windows.UI;
#else
using System.Windows.Media;
#endif

namespace Esponce.QRCode
{
	public class QRCodeClient : QRCodeClientBase
	{
		#region /generate
		/// <summary>
		/// Overloaded. Generates a QR Code image.
		/// </summary>
		/// <param name="content">A message to be encoded in QR code.</param>
		/// <param name="format">Output image format.</param>
		/// <param name="moduleSize">QR code module size in pixels.</param>
		/// <param name="padding">QR code version. Values 1-40 or null to be automatically determined.</param>
		/// <param name="version">Padding value in modules.</param>
		/// <param name="em">Encoding mode.</param>
		/// <param name="ec">Error correction level.</param>
		/// <param name="foreground">ARGB color for foreground or null for default.</param>
		/// <param name="background">ARGB color for background or null for default.</param>
		/// <param name="shorten">Name of URL shortener service or null for none.</param>
		/// <returns>Returns QR Code image stream.</returns>
		public Stream Generate(string content, string format = "png", int moduleSize = 8, int padding = 4, short? version = null, QREncodeMode em = QREncodeMode.Default, QRErrorCorrection ec = QRErrorCorrection.Default, Color? foreground = null, Color? background = null, string shorten = null)
		{
			var model = new QRCodeModel();
			model.Content = content;
			model.Format = format;
			model.Size = moduleSize;
			model.Padding = padding;
			model.Version = version;
			model.EncodeMode = em;
			model.ErrorCorrection = ec;
			model.Foreground = foreground;
			model.Background = background;
			model.Shorten = shorten;
			return Generate(model);
		}

		/// <summary>
		/// Generates a QR Code image.
		/// </summary>
		/// <param name="model">Parameters for generating a QR Code.</param>
		/// <returns>Returns QR Code image stream.</returns>
		public Stream Generate(QRCodeModel model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model");
			}

			string url = UriBuilder.BuildGenerateUrl(model, false);
			string mimeType = QRCodeHelper.GetMimeType(model.Format) + "," + base.GetMimeType();
			var stream = Request("GET", url, model, mimeType);
			return stream;
		}

		/// <summary>
		/// Overloaded. Generates a QR Code asynchronously. Result is returned in the GenerateCompleted event.
		/// </summary>
		/// <param name="content">A message to be encoded in QR code.</param>
		/// <param name="format">Output image format.</param>
		/// <param name="moduleSize">QR code module size in pixels.</param>
		/// <param name="padding">QR code version. Values 1-40 or null to be automatically determined.</param>
		/// <param name="version">Padding value in modules.</param>
		/// <param name="em">Encoding mode.</param>
		/// <param name="ec">Error correction level.</param>
		/// <param name="foreground">ARGB color for foreground or null for default.</param>
		/// <param name="background">ARGB color for background or null for default.</param>
		/// <param name="shorten">Name of URL shortener service or null for none.</param>
		public void GenerateAsync(string content, string format = "png", int moduleSize = 8, int padding = 4, short? version = null, QREncodeMode em = QREncodeMode.Default, QRErrorCorrection ec = QRErrorCorrection.Default, Color? foreground = null, Color? background = null, string shorten = null)
		{
			var model = new QRCodeModel();
			model.Content = content;
			model.Format = format;
			model.Size = moduleSize;
			model.Padding = padding;
			model.Version = version;
			model.EncodeMode = em;
			model.ErrorCorrection = ec;
			model.Foreground = foreground;
			model.Background = background;
			model.Shorten = shorten;
			GenerateAsync(model);
		}

		/// <summary>
		/// Generates a QR Code asynchronously. Result is returned in the GenerateCompleted event.
		/// </summary>
		/// <param name="model">Parameters for generating a QR Code.</param>
		public void GenerateAsync(QRCodeModel model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model");
			}

			string url = UriBuilder.BuildGenerateUrl(model, false);
			string mimeType = QRCodeHelper.GetMimeType(model.Format) + "," + base.GetMimeType();
			RequestAsync("GET", url, model, (error, stream, length) =>
			{
				OnGenerateCompleted(error, stream, length);
			}, mimeType);
		}

		/// <summary>
		/// Occurs when the GenerateAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<GenerateEventArgs> GenerateCompleted;
		protected virtual void OnGenerateCompleted(Exception error, Stream stream, long length)
		{
			if (GenerateCompleted != null)
			{
				GenerateCompleted(this, new GenerateEventArgs()
				{
					Error = error,
					Stream = stream,
					ContentLength = length,
				});
			}
		}
		#endregion

		#region /decode
		/// <summary>
		/// Decodes a QR Code and returns a message.
		/// </summary>
		/// <param name="input">Image stream.</param>
		/// <param name="format">Image format of the stream.</param>
		/// <returns>Returns plain text message.</returns>
		public string Decode(Stream input, string format = "png")
		{
			string url = UriBuilder.BuildDecodeUrl(format);
			var stream = Request("POST", url, input);

			var result = Deserialize<QRCodeModel>(stream);
			string content = result.Content;

			return content;
		}

		/// <summary>
		/// Decodes a QR Code asynchronously and returns a message in the DecodeCompleted event.
		/// </summary>
		/// <param name="input">Image stream.</param>
		/// <param name="format">Image format of the stream.</param>
		public void DecodeAsync(Stream input, string format = "png")
		{
			string url = UriBuilder.BuildDecodeUrl(format);
			RequestAsync("POST", url, input, (error, stream) =>
			{
				QRCodeModel result = null;
				if (error == null && stream != null)
				{
					result = Deserialize<QRCodeModel>(stream);
				}
				OnDecodeCompleted(error, result);
			});
		}

		/// <summary>
		/// Occurs when the DecodeAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<DecodeEventArgs> DecodeCompleted;
		protected virtual void OnDecodeCompleted(Exception error, QRCodeModel result)
		{
			if (DecodeCompleted != null)
			{
				var args = new DecodeEventArgs() { Error = error };
				if (result != null)
				{
					args.Content = result.Content;
					args.Version = result.Version;
					args.ErrorCorrection = result.ErrorCorrection;
					args.Dimension = result.Dimension;
					args.Capacity = result.Capacity;
					args.Length = result.Length;
				}
				DecodeCompleted(this, args);
			}
		}
		#endregion

		#region /track/list
		/// <summary>
		/// Gets a list of campaigns and trackable QR Codes.
		/// </summary>
		/// <returns>Returns list of campaigns and trackable QR Codes.</returns>
		public TrackListResponse GetTrackList()
		{
			string url = UriBuilder.BuildTrackListUrl();
			var stream = Request("GET", url);

			var result = Deserialize<TrackListResponse>(stream);
			return result;
		}

		/// <summary>
		/// Gets a list of campaigns and trackable QR Codes asynchronously. Returns result in the GetTrackListCompleted event.
		/// </summary>
		public void GetTrackListAsync()
		{
			string url = UriBuilder.BuildTrackListUrl();
			RequestAsync("GET", url, null, (error, stream, length) =>
			{
				TrackListResponse result = null;
				if (error == null && stream != null)
				{
					result = Deserialize<TrackListResponse>(stream);
				}
				OnGetTrackListCompleted(error, result);
			});
		}

		/// <summary>
		/// Occurs when the GetTrackListAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<TrackListEventArgs> GetTrackListCompleted;
		protected virtual void OnGetTrackListCompleted(Exception error, TrackListResponse result)
		{
			if (GetTrackListCompleted != null)
			{
				GetTrackListCompleted(this, new TrackListEventArgs()
				{
					Error = error,
					Result = result,
				});
			}
		}
		#endregion

		#region /track/campaign
		public TrackCampaign GetTrackCampaign(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(id);
			var stream = Request("GET", url);

			var result = Deserialize<TrackCampaign>(stream);
			return result;
		}

		public string CreateTrackCampaign(TrackCampaign model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "Campaign model is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl();
			var stream = Request("POST", url, model);

			var result = Deserialize<TrackCampaign>(stream);
			return result.Id;
		}

		public void UpdateTrackCampaign(TrackCampaign model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "Campaign model is missing!");
			}

			if (model.Id == null)
			{
				throw new ArgumentNullException("model.Id", "Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(model.Id);
			var stream = Request("PUT", url, model);
		}

		public void DeleteTrackCampaign(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(id);
			var stream = Request("DELETE", url);
		}

		//GET /track/campaign
		public void GetTrackCampaignAsync(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(id);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				TrackCampaign result = null;

				try
				{
					if (error != null)
					{
						throw error;
					}
					result = Deserialize<TrackCampaign>(stream);
				}
				catch (Exception ex)
				{
					error = ex;
				}
				finally
				{
					OnGetTrackCampaignCompleted(error, result);
				}
			});
		}

		public event EventHandler<TrackCampaignEventArgs> GetTrackCampaignCompleted;
		protected virtual void OnGetTrackCampaignCompleted(Exception error, TrackCampaign campaign)
		{
			if (GetTrackCampaignCompleted != null)
			{
				GetTrackCampaignCompleted(this, new TrackCampaignEventArgs()
				{
					Error = error,
					Campaign = campaign
				});
			}
		}

		//POST /track/campaign
		public void CreateTrackCampaignAsync(TrackCampaign model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "Campaign model is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl();
			RequestAsync("POST", url, model, (error, stream) =>
			{
				TrackCampaign result = null;

				try
				{
					if (error != null)
					{
						throw error;
					}
					result = Deserialize<TrackCampaign>(stream);
				}
				catch (Exception ex)
				{
					error = ex;
				}
				finally
				{
					OnCreateTrackCampaignCompleted(error, result);
				}
			});
		}

		public event EventHandler<TrackCampaignEventArgs> CreateTrackCampaignCompleted;
		protected virtual void OnCreateTrackCampaignCompleted(Exception error, TrackCampaign campaign)
		{
			if (CreateTrackCampaignCompleted != null)
			{
				CreateTrackCampaignCompleted(this, new TrackCampaignEventArgs()
				{
					Error = error,
					Campaign = campaign
				});
			}
		}

		//PUT /track/campaign
		public void UpdateTrackCampaignAsync(TrackCampaign model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "Campaign model is missing!");
			}

			if (model.Id == null)
			{
				throw new ArgumentNullException("model.Id", "Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(model.Id);
			RequestAsync("PUT", url, model, (error, stream) =>
			{
				OnDeleteTrackCampaignCompleted(error, null);
			});
		}

		public event EventHandler<TrackCampaignEventArgs> UpdateTrackCampaignCompleted;
		protected virtual void OnUpdateTrackCampaignCompleted(Exception error, TrackCampaign campaign)
		{
			if (UpdateTrackCampaignCompleted != null)
			{
				UpdateTrackCampaignCompleted(this, new TrackCampaignEventArgs()
				{
					Error = error,
					Campaign = campaign
				});
			}
		}

		//DELETE /track/campaign
		public void DeleteTrackCampaignAsync(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("Campaign id is missing!");
			}

			string url = UriBuilder.BuildTrackCampaignUrl(id);
			RequestAsync("DELETE", url, null, (error, stream) =>
			{
				OnDeleteTrackCampaignCompleted(error, null);
			});
		}

		public event EventHandler<TrackCampaignEventArgs> DeleteTrackCampaignCompleted;
		protected virtual void OnDeleteTrackCampaignCompleted(Exception error, TrackCampaign campaign)
		{
			if (DeleteTrackCampaignCompleted != null)
			{
				DeleteTrackCampaignCompleted(this, new TrackCampaignEventArgs()
				{
					Error = error,
					Campaign = campaign
				});
			}
		}
		#endregion

		#region /track/qrcode
		public TrackQRCode GetTrackQRCode(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("QR Code id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(id);
			var stream = Request("GET", url);

			var result = Deserialize<TrackQRCode>(stream);
			return result;
		}

		public string CreateTrackQRCode(TrackQRCode model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "QR Code model is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl();
			var stream = Request("POST", url, model);

			var result = Deserialize<TrackQRCode>(stream);
			return result.Id;
		}

		public void UpdateTrackQRCode(TrackQRCode model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "QR Code model is missing!");
			}

			if (model.Id == null)
			{
				throw new ArgumentNullException("model.Id", "QR Code id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(model.Id);
			var stream = Request("PUT", url, model);
		}

		public void DeleteTrackQRCode(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("QR Code id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(id);
			var stream = Request("DELETE", url);
		}

		//GET /track/qrcode
		public void GetTrackQRCodeAsync(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("QRCode id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(id);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				TrackQRCode result = null;

				try
				{
					if (error != null)
					{
						throw error;
					}
					result = Deserialize<TrackQRCode>(stream);
				}
				catch (Exception ex)
				{
					error = ex;
				}
				finally
				{
					OnGetTrackQRCodeCompleted(error, result);
				}
			});
		}

		public event EventHandler<TrackQRCodeEventArgs> GetTrackQRCodeCompleted;
		protected virtual void OnGetTrackQRCodeCompleted(Exception error, TrackQRCode campaign)
		{
			if (GetTrackQRCodeCompleted != null)
			{
				GetTrackQRCodeCompleted(this, new TrackQRCodeEventArgs()
				{
					Error = error,
					Result = campaign
				});
			}
		}

		//POST /track/qrcode
		public void CreateTrackQRCodeAsync(TrackQRCode model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "QRCode model is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl();
			RequestAsync("POST", url, model, (error, stream) =>
			{
				TrackQRCode result = null;

				try
				{
					if (error != null)
					{
						throw error;
					}
					result = Deserialize<TrackQRCode>(stream);
				}
				catch (Exception ex)
				{
					error = ex;
				}
				finally
				{
					OnCreateTrackQRCodeCompleted(error, result);
				}
			});
		}

		public event EventHandler<TrackQRCodeEventArgs> CreateTrackQRCodeCompleted;
		protected virtual void OnCreateTrackQRCodeCompleted(Exception error, TrackQRCode campaign)
		{
			if (CreateTrackQRCodeCompleted != null)
			{
				CreateTrackQRCodeCompleted(this, new TrackQRCodeEventArgs()
				{
					Error = error,
					Result = campaign
				});
			}
		}

		//PUT /track/qrcode
		public void UpdateTrackQRCodeAsync(TrackQRCode model)
		{
			if (model == null)
			{
				throw new ArgumentNullException("model", "QRCode model is missing!");
			}

			if (model.Id == null)
			{
				throw new ArgumentNullException("model.Id", "QRCode id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(model.Id);
			RequestAsync("PUT", url, model, (error, stream) =>
			{
				OnDeleteTrackQRCodeCompleted(error, null);
			});
		}

		public event EventHandler<TrackQRCodeEventArgs> UpdateTrackQRCodeCompleted;
		protected virtual void OnUpdateTrackQRCodeCompleted(Exception error, TrackQRCode campaign)
		{
			if (UpdateTrackQRCodeCompleted != null)
			{
				UpdateTrackQRCodeCompleted(this, new TrackQRCodeEventArgs()
				{
					Error = error,
					Result = campaign
				});
			}
		}

		//DELETE /track/qrcode
		public void DeleteTrackQRCodeAsync(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("QRCode id is missing!");
			}

			string url = UriBuilder.BuildTrackQRCodeUrl(id);
			RequestAsync("DELETE", url, null, (error, stream) =>
			{
				OnDeleteTrackQRCodeCompleted(error, null);
			});
		}

		public event EventHandler<TrackQRCodeEventArgs> DeleteTrackQRCodeCompleted;
		protected virtual void OnDeleteTrackQRCodeCompleted(Exception error, TrackQRCode campaign)
		{
			if (DeleteTrackQRCodeCompleted != null)
			{
				DeleteTrackQRCodeCompleted(this, new TrackQRCodeEventArgs()
				{
					Error = error,
					Result = campaign
				});
			}
		}
		#endregion

		#region /track/statistics
		/// <summary>
		/// Gets statistics for a trackable QR Code.
		/// </summary>
		/// <param name="id">QR Code identifier.</param>
		/// <returns>Returns data defined by the format.</returns>
		public List<Record> GetTrackStatistics(string id)
		{
			using (var stream = GetTrackStatistics(id, ExportFormat.JSON))
			{
				using (var reader = new StreamReader(stream))
				{
					string json = reader.ReadToEnd();
					var result = Helper.JsonDeserialize<List<Record>>(json);
					return result;
				}
			}
		}

		/// <summary>
		/// Gets statistics for a trackable QR Code.
		/// </summary>
		/// <param name="id">QR Code identifier.</param>
		/// <param name="format">Export format for statistics.</param>
		/// <returns>Returns data defined by the format.</returns>
		public Stream GetTrackStatistics(string id, ExportFormat format)
		{
			string url = UriBuilder.BuildTrackStatisticsUrl(id, format);
			var stream = Request("GET", url);
			return stream;
		}

		/// <summary>
		/// Gets statistics for a trackable QR Codes and returns result asynchronously in the TrackStatisticsCompleted event.
		/// </summary>
		/// <param name="id">QR Code identifier.</param>
		public void GetTrackStatisticsAsync(string id)
		{
			string url = UriBuilder.BuildTrackStatisticsUrl(id, ExportFormat.JSON);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				using (var reader = new StreamReader(stream))
				{
					string json = reader.ReadToEnd();
					var records = Helper.JsonDeserialize<List<Record>>(json);

					OnGetTrackStatisticsCompleted(new TrackStatisticsEventArgs()
					{
						Error = error,
						Stream = stream,
						Records = records
					});
				}
			});
		}

		/// <summary>
		/// Gets statistics for a trackable QR Codes and returns result asynchronously in the TrackStatisticsCompleted event.
		/// </summary>
		/// <param name="id">QR Code identifier.</param>
		/// <param name="format">Export format for statistics.</param>
		public void GetTrackStatisticsAsync(string id, ExportFormat format)
		{
			string url = UriBuilder.BuildTrackStatisticsUrl(id, format);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				OnGetTrackStatisticsCompleted(new TrackStatisticsEventArgs()
				{
					Error = error,
					Stream = stream
				});
			});
		}

		/// <summary>
		/// Occurs when the TrackStatisticsAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<TrackStatisticsEventArgs> GetTrackStatisticsCompleted;
		protected virtual void OnGetTrackStatisticsCompleted(TrackStatisticsEventArgs args)
		{
			if (GetTrackStatisticsCompleted != null)
			{
				GetTrackStatisticsCompleted(this, args);
			}
		}
		#endregion

		#region /track/export
		/// <summary>
		/// Exports campaigns and trackable QR Codes.
		/// </summary>
		/// <param name="format">Export format.</param>
		/// <returns>Returns data defined by the format.</returns>
		public List<TrackDataModel> ExportTrackRecords()
		{
			using (var stream = ExportTrackRecords(ExportFormat.JSON))
			{
				using (var reader = new StreamReader(stream))
				{
					string json = reader.ReadToEnd();
					var result = Helper.JsonDeserialize<List<TrackDataModel>>(json);
					return result;
				}
			}
		}

		/// <summary>
		/// Exports campaigns and trackable QR Codes.
		/// </summary>
		/// <param name="format">Export format.</param>
		/// <returns>Returns data defined by the format.</returns>
		public Stream ExportTrackRecords(ExportFormat format)
		{
			string url = UriBuilder.BuildTrackExportUrl(format);
			var stream = Request("GET", url);
			return stream;
		}

		/// <summary>
		/// Exports campaigns and trackable QR Codes and returns result asynchronously in the TrackExportCompleted event.
		/// </summary>
		/// <param name="format">Export format.</param>
		public void ExportTrackRecordsAsync()
		{
			string url = UriBuilder.BuildTrackExportUrl(ExportFormat.JSON);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				using (var reader = new StreamReader(stream))
				{
					string json = reader.ReadToEnd();
					var items = Helper.JsonDeserialize<List<TrackDataModel>>(json);

					OnExportTrackRecordsCompleted(new TrackExportEventArgs()
					{
						Error = error,
						Stream = stream,
						Items = items
					});
				}
			});
		}

		/// <summary>
		/// Exports campaigns and trackable QR Codes and returns result asynchronously in the TrackExportCompleted event.
		/// </summary>
		/// <param name="format">Export format.</param>
		public void ExportTrackRecordsAsync(ExportFormat format)
		{
			string url = UriBuilder.BuildTrackExportUrl(format);
			RequestAsync("GET", url, null, (error, stream) =>
			{
				OnExportTrackRecordsCompleted(new TrackExportEventArgs()
				{
					Error = error,
					Stream = stream
				});
			});
		}

		/// <summary>
		/// Occurs when the TrackExportAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<TrackExportEventArgs> ExportTrackRecordsCompleted;
		protected virtual void OnExportTrackRecordsCompleted(TrackExportEventArgs args)
		{
			if (ExportTrackRecordsCompleted != null)
			{
				ExportTrackRecordsCompleted(this, args);
			}
		}
		#endregion

		#region /track/import
		/// <summary>
		/// Imports campaigns and trackable QR Codes.
		/// </summary>
		/// <param name="input">List of items to import.</param>
		/// <returns>Returns data defined by the format.</returns>
		public List<TrackImportFeedback> ImportTrackRecords(IEnumerable<TrackDataModel> list)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}

			string json = Helper.JsonSerialize(list);
			byte[] buffer = Encoding.UTF8.GetBytes(json);
			using (var stream = new MemoryStream(buffer))
			{
				return ImportTrackRecords(stream, ImportFormat.JSON);
			}
		}

		/// <summary>
		/// Imports campaigns and trackable QR Codes.
		/// </summary>
		/// <param name="input">Data to upload.</param>
		/// <param name="format">Format of data to upload.</param>
		/// <returns>Returns data defined by the format.</returns>
		public List<TrackImportFeedback> ImportTrackRecords(Stream input, ImportFormat format)
		{
			string url = UriBuilder.BuildTrackImportUrl(format);
			using (var stream = Request("POST", url, input))
			{
				using (var reader = new StreamReader(stream))
				{
					string json = reader.ReadToEnd();
					var result = Helper.JsonDeserialize<List<TrackImportFeedback>>(json);
					return result;
				}
			}
		}

		/// <summary>
		/// Imports campaigns and trackable QR Codes and returns result asynchronously in the TrackImportCompleted event.
		/// </summary>
		/// <param name="input">List of items to import.</param>
		public void ImportTrackRecordsAsync(IEnumerable<TrackDataModel> list)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}

			string json = Helper.JsonSerialize(list);
			byte[] buffer = Encoding.UTF8.GetBytes(json);
			var stream = new MemoryStream(buffer);
			ImportTrackRecordsAsync(stream, ImportFormat.JSON);
		}

		/// <summary>
		/// Imports campaigns and trackable QR Codes and returns result asynchronously in the TrackImportCompleted event.
		/// </summary>
		/// <param name="input">Data to upload.</param>
		/// <param name="format">Format of data to upload.</param>
		public void ImportTrackRecordsAsync(Stream input, ImportFormat format)
		{
			string url = UriBuilder.BuildTrackImportUrl(format);
			RequestAsync("POST", url, input, (error, stream) =>
			{
				if (error != null)
				{
					OnImportTrackRecordsCompleted(error, null);
					return;
				}

				List<TrackImportFeedback> results = null;

				try
				{
					using (var reader = new StreamReader(stream))
					{
						string json = reader.ReadToEnd();
						results = Helper.JsonDeserialize<List<TrackImportFeedback>>(json);
					}
				}
				catch (Exception ex)
				{
					error = ex;
				}
				finally
				{
					OnImportTrackRecordsCompleted(error, results);
				}
			});
		}

		/// <summary>
		/// Occurs when the TrackImportAsync method returns result asynchronously.
		/// </summary>
		public event EventHandler<TrackImportEventArgs> ImportTrackRecordsCompleted;
		protected virtual void OnImportTrackRecordsCompleted(Exception error, List<TrackImportFeedback> results)
		{
			if (ImportTrackRecordsCompleted != null)
			{
				ImportTrackRecordsCompleted(this, new TrackImportEventArgs()
				{
					Error = error,
					Results = results
				});
			}
		}
		#endregion
	}
}
